package org.jugile.restapi.domain;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jugile.restapi.Match;
import org.jugile.restapi.common.*;
import org.jugile.util.Buffer;
import org.jugile.util.Node;
import org.jugile.util.Props;
import org.jugile.util.Proxy;

public class PathItem extends PathItemBase implements org.jugile.util.IDO {

	protected boolean match(Match m, int idx) {
		//print("  match: "+ m.getPath(idx) + " == "+getStr() + " " + getRegexp());
		if (test(m,idx)) {
			setVars(m,idx);
			if (m.isNotLast(idx)) {
				for (PathItem pi : getPathItems().sort("id")) {
					if (pi.match(m, idx+1)) return true;
				}
			} else {
                // check params
                for (PathItem p : getParams().sort("id")) {
                	if (m.getParams().get(p.getName()) != null) {
                		Operation op2 = p.findOperation(m.getMethod(), m.getType());
                		if (op2 == null) return false;
                		m.setOp(op2);
            			p.setVars(m,idx);
                		return true;
                	}
                }
				Operation op = findOperation(m.getMethod(), m.getType());
				if (op == null) return false;
				m.setOp(op);
				return true; // match: all matched and last item
			}
		}
		return false;
	}
	
	private void setVars(Match m, int idx) {
		if (!empty(getVar())) {
			if (m.getIsStopped()) {
				m.setRest(getVar(), idx);
			} else {
				if (getType() == PathItemType.PATH) m.set(getVar(),idx);
				else m.set(getVar(), m.getParams().get(getName()));
			}
		}
		for (Var v : getVars()) {
			m.set(v.getName(), v.getValue());
		}
	}
	
	private boolean test(Match m, int idx) {
		if (!empty(getCondition())) {
			Proxy app = new Proxy(m.getApp());
			boolean b = (Boolean)app.call(getCondition(), m, m.getPath(idx));
			if (b) return true;
			return false;
		}
		if (!empty(getStr())) {
			if (getStr().endsWith("*")) {
				String part = getStr().substring(0,getStr().length()-1);
				m.setIsStopped();
				if (part.length() == 0) return true;
				if (m.getPath(idx).startsWith(part)) return true;
				return false;
			}
			if (getStr().equals(m.getPath(idx))) return true;
			for (String key : translations.keySet()) {
				if (translations.get(key).equals(m.getPath(idx))) {
					m.setLang(key);
					return true;
				}
			}
		}
		if (!empty(getRegexp())) {
			String rexp = getRegexp();
			if (rexp.indexOf('(') >= 0) rexp = substParts(rexp,null);
			Pattern p = Pattern.compile(rexp);
			Matcher mz = p.matcher(m.getPath(idx));
			if (mz.matches()) {
				if (mz.groupCount() > 0) {
					for (int i = 1; i <= mz.groupCount(); i++) {
						m.set(getPartVarName(i), mz.group(i));
					}
				}
				return true;
			}
		}
		return false;
	}
	
	private String doubleBackSlash(String str) {
		return str.replaceAll("\\\\", "\\\\\\\\");
	}
	
	private String substParts(String str, Map valuesp) {
		Map<String,String> values = (Map<String,String>)valuesp; // capes1 bug
		Pattern p = Pattern.compile("\\(([a-zA-Z0-9]+)\\)");
		Matcher m = p.matcher(str);
		StringBuffer sb = new StringBuffer();
		int idx = 1;
		while (m.find()) {
			String name = m.group(1);
			Part part = getPart(name);
			part.setIdx(idx++);
			String value = part.getRegexp();
			if (values != null) {
				value = values.get(name);
				if (value != null) m.appendReplacement(sb, value);
			} else {
				value = doubleBackSlash(value);
				m.appendReplacement(sb, "("+value+")");
			}
		}
		m.appendTail(sb);
		return sb.toString();
	}
	
	private String getTmplSuffix() {
		String sfx = Props.get("jugile.web.tmpl.suffix",".xml");
		//print("getTmplSuffix: "+sfx);
		return sfx;
	}
	
	protected String resolveTmpl(String tmplname) {
		if (empty(tmplname)) tmplname = resolveCtrlName() + getTmplSuffix();
		return resolveTmplDir() + "/" + tmplname;
	}
	protected String resolveModel(String modelname) {
		if (empty(modelname)) modelname = resolveCtrlName() + "_model.xml";
		return resolveModelDir() + "/" + modelname;
	}
	protected String resolveModelName() {
		return resolveCtrlName() + "_model.xml";
	}

	protected String resolveTmplDir() {
		if (!empty(getTmplDir())) return getTmplDir();
		PathItem pi = getParent();
		if (pi != null) return pi.resolveTmplDir();
		return null;		
	}
	protected String resolveModelDir() {
		if (!empty(getModelDir())) return getModelDir();
		PathItem pi = getParent();
		if (pi != null) return pi.resolveModelDir();
		return null;		
	}
	protected String resolveCtrlName() {
		if (!empty(getController())) return getController();
		return resolveDefaultCtrlName();
	}
	protected String resolveControllerClassName() {
		return resolvePkg() + "." + resolveCtrlName();
	}
	protected String resolveDefaultCtrlName() {
		if (!empty(getDcontroller())) return getDcontroller();
		PathItem pi = getParent();
		if (pi != null) return pi.resolveDefaultCtrlName();
		return null;
	}
	protected String resolvePkg() {
		if (!empty(getPkg())) return getPkg();
		PathItem pi = getParent();
		if (pi != null) return pi.resolvePkg();
		return null;		
	}
	protected String resolveBorderFile() {
		if (!empty(getBorder())) return resolveTmplDir() + "/" + getBorder();
		PathItem pi = getParent();
		if (pi != null) return pi.resolveBorderFile();
		return null;
	}
	

	public Var createVar() {
		return d().createVar().setPathItem(this);
	}
	
	public Operation createOperation() {
		return d().createOperation().setPathItem(this);
	}
	public Part createPart() {
		return d().createPart().setPathItem(this);		
	}
	public PathItem createSubPathItem() {
		return d().createPathItem().setType(PathItemType.PATH).setParent(this);
	}
	public Part getPart(String varname) {
		return getParts().byVar(varname).unique();
	}
	public String getPartVarName(int idx) {
		return getParts().byIdx(idx).unique().getVar();
	}
	public PathItem createParam() {
		return d().createPathItem().setType(PathItemType.PARAM).setParent(this);
	}
	public PathItemCollection getPathItems() {
		return getChildren().byType(PathItemType.PATH);
	}
	public PathItemCollection getParams() {
		return getChildren().byType(PathItemType.PARAM);
	}
	public boolean isParam() {
		return getType() == PathItemType.PARAM;
	}
	
	public Operation findOperation(String method, String type) {
		return getOperations().byMatch(method, type).first("id");
	}

	public Api getApi() {
		PathItem p = getParent();
		if (p != null) return p.getApi();
		return super.getApi();
	}

	public PathItemCollection getAllPathItems() {
		PathItemCollection pic = new PathItemCollection();
		for (PathItem pi : getPathItems()) pic.add(pi.getAllPathItems());
		pic.add(this);
		return pic;
	}

	public String getFullPath(String lang, Map vars) {
		PathItem p = getParent();
		if (p == null) return getPathPart(lang,vars);
		return p.getFullPath(lang,vars) + "/" + getPathPart(lang,vars);		
	}
	
	public String getPathPart(String lang, Map vars) {
		if (!empty(getStr())) {
			if (!empty(lang) && !"fi".equals(lang)) return translations.get(lang);
			return getStr();
		}
		if (!empty(getRegexp())) {
			String rexp = getRegexp();
			if (rexp.indexOf('(') >= 0) return substParts(rexp,vars);
			else return (String)vars.get(getVar());
		}
		return "---";
	}
	
	public String dumpFullPath(String lang) {
		PathItem p = getParent();
		if (p == null) return dumpPathPart(lang);
		return p.dumpFullPath(lang) + "/" + dumpPathPart(lang);		
	}
	
	public String dumpPathPart(String lang) {
		if (!empty(getStr())) {
			if (!empty(lang) && !"fi".equals(lang)) return translations.get(lang);
			return getStr();
		}
		if (!empty(getRegexp())) {
			String rexp = getRegexp();
			return rexp;
		}
		if (!empty(getCondition())) {
			return "[if("+getCondition()+"())]";
		}
		return "---";
	}
	
	public boolean isMatchName(String name) {
		if (name.equals(getUiName())) return true;
		return false;
	}
	
	public String getUiName() {
		if (!empty(getName())) return getName();
		return getController();
	}
	
	private Map<String,String> translations = new HashMap<String,String>();
	
	public void parse(Node n) {
		setStr(n.attr("str"));
		setName(n.attr("name"));
		setRegexp(n.attr("rexp"));
		setController(n.attr("ctrl"));
		setRedirect(n.attr("redirect"));
		setVar(n.attr("var"));
		setCondition(n.attr("if"));
		
		setPkg(n.str("package"));
		setTmplDir(n.str("tmpl-dir"));
		setModelDir(n.str("model-dir"));
		setDcontroller(n.str("default-ctrl"));
		if (!empty(n.str("border"))) setBorder(n.str("border"));

		for (Node gn : n.list("get")) createOperation().parse(gn);
		for (Node gn : n.list("post")) createOperation().parse(gn);
		for (Node pn : n.list("part")) createPart().parse(pn);
		for (Node pn : n.list("param")) createParam().parse(pn);
		for (Node pn : n.list("var")) createVar().parse(pn);

		for (Node pn : n.list("path")) createSubPathItem().parse(pn);
		
		// localications
		for (String key : n.attrs().keySet()) {
			if (key.startsWith("str:")) {
				String lang = key.substring(4);
				translations.put(lang, n.attr(key));
			}
		}
	}
	
	public Node toXml() {
		Node n = null;
		if (this.getType() == PathItemType.PARAM) n = new Node("param");
		else n = new Node("path");
		if (!empty(getStr())) n.attr("str",getStr());
		if (!empty(getRegexp())) n.attr("rexp",getRegexp());
		if (!empty(getName())) n.attr("name",getName());
		if (!empty(getController())) n.attr("ctrl",getController());
		if (!empty(getRedirect())) n.attr("redirect",getRedirect());
		if (!empty(getVar())) n.attr("var",getVar());
		if (!empty(getCondition())) n.attr("if",getCondition());		
		
		if (!empty(getPkg())) n.set("package", getPkg());
		if (!empty(getTmplDir())) n.set("tmpl-dir", getTmplDir());
		if (!empty(getModelDir())) n.set("model-dir", getModelDir());
		if (!empty(getDcontroller())) n.set("default-ctrl", getDcontroller());
		if (!empty(getBorder())) n.set("border", getBorder());
		
		for (Operation o : getOperations().sort("id")) n.add(o.toXml());
		for (Part p : getParts().sort("id")) n.add(p.toXml());
		for (PathItem p : getParams().sort("id")) n.add(p.toXml());		
		for (PathItem p : getPathItems().sort("id")) n.add(p.toXml());
		for (Var v : getVars().sort("id")) n.add(v.toXml());
		
		// localisations
		for (String key : translations.keySet()) {
			n.attr("str:"+key,translations.get(key));
		}
		
		return n;
	}
	
	
	public void dump(Buffer buf, String lang) {
		buf.incrIndent();

		if (isParam()) {
			buf.ln("?" + getName());
			buf.decrIndent();
			return;
		}
		
		buf.ln("/" + this.dumpPathPart(lang));
		
		for (Operation o : getOperations().sort("id")) o.dump(buf);

		for (PathItem p : getParams().sort("id")) p.dump(buf,lang);		
		for (PathItem p : getPathItems().sort("id")) p.dump(buf,lang);
		buf.decrIndent();
	}

}